package org.hq.upvideo;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.PostConstruct;
import org.hq.pojo.GroupsVideo;
import org.hq.pojo.HostWeb;
import org.hq.pojo.VideoMessage;
import org.hq.pojo.WebName;
import org.hq.service.sql.MysqlManageService;
import org.hq.util.OkHttps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;

import static org.hq.upvideo.RedisKey.UP_VVIDE_MESSAGE_TASK_SAVE_SIHU_KEY;

/**
 * ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
 * (核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略)
 * 参数一：核心线程数量              不能小于0
 * 参数二：最大线程数                不能小于0，最大数量 >= 核心线程数量
 * 参数三：空闲线程最大存活时间       不能小于0
 * 参数四：时间单位                  用TimeUnit指定
 * 参数五：任务队列                  不能为null
 * 参数六：创建线程工厂              不能为null
 * 参数七：任务的拒绝策略             不能为null
 */
@Component
public class UpVideoService implements IupVideoService {
    //核心线程数量
    private final int corePoolSize = 8;
    // 最大线程数
    private final int maximumPoolSize = 16;
    // 最大任务数量
    private final int taskSize = 32;
    // 2 分钟 空闲线程最大存活时间
    private final long keepAliveTime = 2;
    //redis 工具类
    private final RedisTemplate<String, String> redis;
    //数据库管理对象
    private final MysqlManageService sqls;
    // http 浏览器工具
    private OkHttps http;
    private ThreadPoolExecutor pool;

    public UpVideoService(@Autowired RedisTemplate<String, String> redisString, @Autowired MysqlManageService sqls, @Autowired OkHttps http) {
        this.redis = redisString;
        this.sqls = sqls;
        this.http = http;
    }


    /**
     * 服务初始化 服务
     */
    @PostConstruct
    private void init() {
        /**
         * 8, 核心线程数量，能小于0
         * 16,  最大线程数，不能小于0，最大数量 >= 核心线程数量
         * 2 分钟 空闲线程最大存活时间
         * 64 最大任务队列
         * 服务初始化 服务
         */
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(this.taskSize);
        ThreadPoolExecutor.AbortPolicy policy = new ThreadPoolExecutor.AbortPolicy();
        this.pool = new ThreadPoolExecutor(this.corePoolSize, this.maximumPoolSize, this.keepAliveTime, TimeUnit.MINUTES, queue, policy);

    }

    /**
     * redis list 集合保存
     *
     * @param key 建
     * @param obj 保存对象
     */
    private long redisSaveList(String key, Object obj) {
        String json = JSON.toJSONString(obj);
        return this.redis.opsForList().rightPush(key, json);
    }

    /**
     * 获取 redis 服务器 list 集合数据
     * 获取第一条数据
     *
     * @param key redis
     * @return 返回任务对象
     */
    private <T> T redisGetListLeftPop(String key, Class<T> claz) {
        if (this.redis.opsForList().size(key) > 0) {
            String json = this.redis.opsForList().leftPop(key);
            if (json == null) return null;//获取数据失败
            return JSON.parseObject(json, claz);
        }

        return null;
    }

    /**
     * 更新数据
     */
    public void update() {
        //1 获取所以网站对象
        List<WebName> lwn = sqls.getLisetWebName();

        for (WebName wn : lwn) {
            //获取最新网址
            HostWeb hw = sqls.getLimitHostWeb(wn);
            //获取网络最新网址
            String uir = UpVideoTaskSiHu.getUir(hw.getPath());
            if (uir == null) return;
            //判断是否是最新网址如果不是就更新网址
            if (!uir.equals(hw.getPath())) {
                HostWeb uphost = new HostWeb();
                uphost.setPath(uir);
                uphost.setM3u8("https://m3u8.93cdn.com");

                //更新数据库
                this.sqls.addHostWeb(wn.getName(), uphost);
                hw.setPath(uir);
            }
            String key = String.valueOf(RedisKey.ADD_VIDEO_ITEM_SIHU_KEY);
            //删除先前数据保存
            this.redis.delete(key);
            int pageSize = 2000;
            Page<VideoMessage> page = this.sqls.pageVideoMessage(1, pageSize);
            for (int i = 2; i <= page.getPages(); i++) {
                Set<String> setJson = new HashSet<>();
                for (VideoMessage record : page.getRecords()) {
                    VideoTaskEntity entity = new VideoTaskEntity();
                    entity.setHtmlweb(record.getHtmlweb());
                    String json = JSON.toJSONString(entity);
                    setJson.add(json);

                }
                // 存储 videoHtmpPath 路径用于检测数据库此视频已存在无需添加
                this.redis.opsForSet().add(key, setJson.toArray(new String[0]));

                page = this.sqls.pageVideoMessage(i, pageSize);


            }
            //最后一页面
            Set<String> setJson = new HashSet<>();
            for (VideoMessage record : page.getRecords()) {
                VideoTaskEntity entity = new VideoTaskEntity();
                entity.setHtmlweb(record.getHtmlweb());
                String json = JSON.toJSONString(entity);
                setJson.add(json);

            }
            // 存储 videoHtmpPath 路径用于检测数据库此视频已存在无需添加
            if (setJson.size() > 0) {
                this.redis.opsForSet().add(key, setJson.toArray(new String[0]));
            }


            //获取子分组集合
            List<GroupsVideo> gvi = this.sqls.getListGroupsItme(wn);
            for (GroupsVideo gv : gvi) {
                //初始化任务对象数据
                VideoTaskEntity en = new VideoTaskEntity();
                //网址
                en.setWebHost(hw.getPath());
                // 分组 id
                en.setGroupId(gv.getId());
                // 任务路径
                en.setTaskPath(gv.getPath());
                // 任务类型
                en.setTaskType(EupVideoTaskType.PARSE_HTML_SIHU_PAGE_COUNT);
                //任务提交
                this.submit(en);


            }

        }
    }

    /**
     * 任务出错处理
     * 将任务数据保存到 redis 服务器
     *
     * @param entity 任务数据
     */
    @Override
    public void error(VideoTaskEntity entity) {
        String json = JSON.toJSONString(entity);
        String key = String.valueOf(RedisKey.PARSE_HTML_SIHU_ERROR_KEY);
        this.redis.opsForSet().add(key, json);
        return;

    }


    /**
     * 任务完成处理剩余任务
     * 读取 redis 服务器 任务数据对象重新执行
     */
    @Override
    public synchronized final void finish() {
        //判断任务队列是否满
        if (this.pool.getActiveCount() > this.maximumPoolSize)
            return;
        // 1 优先执行任务
        String key = String.valueOf(RedisKey.UP_VVIDE_TASK_SAVE_SIHU_KEY);
        VideoTaskEntity entity = this.redisGetListLeftPop(key, VideoTaskEntity.class);
        if (entity == null) {
            //最后一个任务将 redis 视频信息 保存到 mysql 去
            if (1 >= this.pool.getActiveCount()) {
                this.saveVideoMessageSql();

            }
            return;
        }
        try {
            // 提交任务立即执行
            this.pool.execute(new UpVideoTaskSiHu(entity));
        } catch (RejectedExecutionException e) {
            //任务队列已满 将任务数据保存到 redisk
            this.redisSaveList(key, entity);
        }
    }

    /**
     * 任务提交
     *
     * @param entity 任务对象
     */
    @Override
    public synchronized final void submit(VideoTaskEntity entity) {
        //判断任务队列是否已满
        if (this.pool.getActiveCount() >= this.maximumPoolSize) {
            //任务队列已满 将任务数据保存到 redisk
            String key = String.valueOf(RedisKey.UP_VVIDE_TASK_SAVE_SIHU_KEY);
            this.redisSaveList(key, entity);
            return;
        }
        try {
            // 提交任务立即执行
            this.pool.execute(new UpVideoTaskSiHu(entity));
            return;
        } catch (RejectedExecutionException e) {

            return;
        }
    }

    /**
     * get 同步 请求
     *
     * @param path 请求路径
     * @return 成功返回数据 失败返回 null
     */
    @Override
    public String getRequestToStringSync(String path) throws IOException {
        return http.getRequestToStringSync(path);
    }

    /**
     * 添加 一条视频条目 到 redisk
     *
     * @param entity 数据对象
     * @return 成功返回 1 失败 0
     */
    @Override
    public synchronized final Long addVideoItem(VideoTaskEntity entity) {
        String json = JSON.toJSONString(entity);
        String key = String.valueOf(RedisKey.ADD_VIDEO_ITEM_SIHU_KEY);
        return this.redis.opsForSet().add(key, json);
    }

    /**
     * 视频信息添加到 redis 服务器
     *
     * @param entity 数据对象
     * @return 成功返回 1 失败 0
     */
    @Override
    public Long addVideoMessageRedis(VideoTaskEntity entity) {
        String key = String.valueOf(RedisKey.UP_VVIDE_MESSAGE_TASK_SAVE_SIHU_KEY);
        return this.redisSaveList(key, entity);
    }

    /**
     * 分组标签添加
     *
     * @param tagName     标签名称
     * @param groupsVideo 视频分组对象
     * @param tagType     标签类型
     * @return 成功 true 失败 false
     */
    @Override
    public boolean saveGroupsVideo(String tagName, GroupsVideo groupsVideo, int tagType) {
        return this.sqls.saveGroupsVideo(tagName, groupsVideo, tagType);
    }

    /**
     * 更新视频任务完成
     * 将视频信息保存到 mysql 数据库去
     */
    private void saveVideoMessageSql() {
        String key = String.valueOf(UP_VVIDE_MESSAGE_TASK_SAVE_SIHU_KEY);
        //获取 redisk 所以数据
        List<String> jsonlst = this.redis.opsForList().leftPop(key, 2000);
        while (jsonlst.size() > 0) {
            //将 json 数据集合 实例化为对象
            List<VideoMessage> vmlist = JSON.parseArray(jsonlst.toString(), VideoMessage.class);
            //将数据存储到数据库中
            this.sqls.saveVideoMessage(vmlist);
            //重新读取数据
            jsonlst = this.redis.opsForList().leftPop(key, 2000);
        }


        //删除先前数据保存
        this.redis.delete(String.valueOf(RedisKey.ADD_VIDEO_ITEM_SIHU_KEY));
    }


}
